/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "spatial_slave_epoch.h"

#include <thread>
#include "hilog_wrapper.h"

namespace OHOS {
namespace Msdp {
SpatialSlaveEpoch::SpatialSlaveEpoch(std::shared_ptr<SpatialMasterEpoch> master)
    : SpatialAwarenessEpoch(master->handler_, master->communicator_, master->coordinateSystem_)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Create Slave from Master. master->sessionId_: %{public}d", master->sessionId_);
    this->coordinateSystem_ = master->coordinateSystem_;
    this->handler_ = master->handler_;
    this->communicator_ = master->communicator_;
}

void SpatialSlaveEpoch::Start()
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialSlaveEpoch Start");
    listener_ = std::make_shared<SlaveListener>(shared_from_this());
    communicator_->RegisterSessionListener(listener_);
    HandleDeviceState();
    SendEvent(EVENT_EPOCH_SLAVE_START);
    SendEvent(EVENT_SLAVE_EPOCH_LIFETIME_OUT, 0, GetLifetime());
    alive_ = true;
}

uint32_t SpatialSlaveEpoch::GetEpoch()
{
    return EPOCH_SLAVE;
}

bool SpatialSlaveEpoch::HandleEvent(int32_t event, int64_t param)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "SpatialSlaveEpoch Event: %{public}d", event);
    switch (event) {
        case EVENT_SLAVE_EPOCH_LIFETIME_OUT:
            alive_ = false;
            End();
            return true;
        default:
            break;
    }
    return false;
}

void SpatialSlaveEpoch::End()
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialSlaveEpoch End");
    RemoveEvent(EVENT_SLAVE_EPOCH_LIFETIME_OUT);
    communicator_->UnregisterDeviceStateListener();
    SendEvent(EVENT_EPOCH_SLAVE_END);
}

void SpatialSlaveEpoch::HandleGetMapRequest(std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Slave HandleGetMapRequest.request->sessionId: %{public}d", request->sessionId);
    communicator_->GetCommunicatorSession(request->sessionId).get()->ResponseGetMap(coordinateSystem_->Commit());
    return;
}

void SpatialSlaveEpoch::HandleSyncMapOfferRequest(std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Slave HandleSyncMapOfferRequest");
    if (coordinateSystem_->GetTopCommit() == request->value) {
        communicator_->GetCommunicatorSession(request->sessionId).get()->ResponseSyncMapOffer(true);
    } else {
        communicator_->GetCommunicatorSession(request->sessionId).get()->ResponseSyncMapOffer(false);
    }
    return;
}

void SpatialSlaveEpoch::HandleSyncMapRequest(std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialSlaveEpoch HandleSyncMapRequest");
    std::shared_ptr<SpatialMap> map = request->map;
    bool ret = coordinateSystem_->SyncMap(map);
    communicator_->GetCommunicatorSession(request->sessionId).get()->ResponseSyncMap(ret);
    return;
}

void SpatialSlaveEpoch::SlaveListener::OnSessionOpen(
    std::shared_ptr<SpatialCommunicator::Session> session)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Slave OnSessionOpen: %{public}d", session->GetSessionId());
    
    std::shared_ptr<SpatialSlaveEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "slave epoch is null");
        return;
    }

    int role = session->GetRole();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s device role: %{public}d", __func__, role);
    if (role != SpatialCommunicator::ROLE_CLIENT) {
        SPATIAL_LOGI(MODULE_SERVICE, "Slave as server");
    } else {
        epoch->End();
    }
}

void SpatialSlaveEpoch::SlaveListener::OnSessionClose(
    std::shared_ptr<SpatialCommunicator::Session> session)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Slave OnSessionClose: %{public}d", session->GetSessionId());
    std::shared_ptr<SpatialSlaveEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "master_ epoch is null");
        return;
    }
    if (!epoch->alive_) {
        epoch->End();
    }
}

void SpatialSlaveEpoch::SlaveListener::OnReceiveRequest(
    std::shared_ptr<SpatialCommunicator::Session> session,
    std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Slave OnReceiveRequest: %{public}d, request->request=%{public}d",
        session->GetSessionId(), request->requestId);
    std::shared_ptr<SpatialSlaveEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        return;
    }
    switch (request->requestId) {
        case SpatialRequestId::REQUEST_GET_MAP:
            epoch->HandleGetMapRequest(request);
            break;
        case SpatialRequestId::REQUEST_SYNC_MAP_OFFER:
            epoch->HandleSyncMapOfferRequest(request);
            break;
        case SpatialRequestId::REQUEST_SYNC_MAP:
            epoch->HandleSyncMapRequest(request);
            break;
        default:
            break;
    }
}

void SpatialSlaveEpoch::SlaveListener::OnReceiveResponse(
    std::shared_ptr<SpatialCommunicator::Session> session,
    std::shared_ptr<SpatialResponse> response)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Slave OnReceiveResponse: %{public}d", session->GetSessionId());
}

void SpatialSlaveEpoch::HandleDeviceState()
{
    communicator_->RegisterDeviceStateListener([this](DeviceStateEvent event, const DeviceInfo& device) {
        switch (event) {
            case DeviceStateEvent::ONLINE:
                OnDeviceOnline(device);
                break;
            case DeviceStateEvent::OFFLINE:
                OnDeviceOffline(device);
                break;
            case DeviceStateEvent::CHANGED:
                OnDeviceChanged(device);
                break;
            case DeviceStateEvent::READY:
                OnDeviceReady(device);
                break;
            default:
                break;
        }
    });
}

void SpatialSlaveEpoch::OnDeviceOnline(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Slave OnDeviceOnline: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }
    for (auto &deviceIter : foundDevices_) {
        if (deviceIter.deviceId == device.deviceId) {
            SPATIAL_LOGI(MODULE_SERVICE, "device is exist");
            exist_ = true;
        }
    }
    if (!exist_) {
        foundDevices_.push_back(device);
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}

void SpatialSlaveEpoch::OnDeviceReady(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Slave OnDeviceReady: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }

    for (auto &deviceIter : foundDevices_) {
        if (deviceIter.deviceId == device.deviceId) {
            deviceIter = device;
            exist_ = true;
        }
    }
    if (exist_) {
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}

void SpatialSlaveEpoch::OnDeviceOffline(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Slave OnDeviceOffline: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }
    for (auto iter = foundDevices_.begin(); iter != foundDevices_.end();) {
        if (iter->deviceId == device.deviceId) {
            iter = foundDevices_.erase(iter);
            exist_ = true;
        } else {
            iter++;
        }
    }
    if (exist_) {
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}

void SpatialSlaveEpoch::OnDeviceChanged(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Slave OnDeviceChanged: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }
    for (auto &deviceIter : foundDevices_) {
        if (deviceIter.deviceId == device.deviceId) {
            deviceIter = device;
            exist_ = true;
        }
    }
    if (exist_) {
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}
} // namespace Msdp
} // namespace OHOS